<?php

	class rsrcHandle{
		
		protected $rsrc, $handle, $parent, $destroy, $mode;
		
		protected $is_smuoi = false;
		
		protected $is_valid = false;
		
		protected $fexists = false;

		// skip: 1 = skip fmgr search/instantiation, 2: destroy on close()
		function __construct($rsrc, $mode='r', $skip=0){
			$this->mode = $mode;
			switch(true){
				case (!$skip && smuoi_is_file($rsrc)):
					$this->rsrc = $rsrc;
					$this->fexists = $this->is_valid = $this->is_smuoi = true;
					return;
				case is_string($rsrc):
					$path = $this->rsrc = path_resolve($rsrc);
					if(!$skip && ($file = sm_open($path))){
						if(!smuoi_is_file($file)) throw new Exception("rsrcHandle: resource ($path) is not a file");
						$this->rsrc = $file;
						$this->fexists = $this->is_valid = $this->is_smuoi = true;	
						return;
					}
					$path = path_rewind($path);
					if(!$skip && ($parent = sm_open($path))){
						if(!smuoi_is_dir($parent)) throw new Exception("rsrcHandle: resource ($path) is not a directory");
						$this->parent = $parent;
						$this->is_valid = $this->is_smuoi = true;
						return;
					}
					$this->destroy = ($skip > 1);
					if(is_file($this->rsrc)){
						$this->fexists = $this->is_valid = true;
						return;
					}
					if(is_dir($path)){
						$this->parent = $path;
						$this->is_valid = true;
						return;
					}
				default:
					throw new Exception("rsrcHandle: invalid resource ($rsrc)");		
			}
		}
				
		public static function fmethods(){
			static $fmethods = array(
				'read',
				'write',
				'seek',
				'gets',
				'getss',
				'tell',
				'stat',
				'eof',
				'truncate',
				'flush',
				'scanf',
				'putcsv',
				'passthru',
				'getc',
				'getcsv',
				'lock'			 
			);
			return $fmethods;
		}
		
		function __call($func, $params){
			if(!in_array($func, rsrcHandle::fmethods())){
				trigger_error(get_class($this).": $func is not a callable method", E_USER_ERROR);
				return;
			}
			if(!$this->handle) throw new Exception("rsrcHandle: no handle");
			array_unshift($params, $this->handle);
			return call_user_func_array('f'.$func, $params); 	
		}
		
		function exists(){
			return is_file($this->realpath());	
		}
		
		function realpath(){
			if(!$this->is_smuoi) return $this->rsrc;
			return (!$this->fexists) ? $this->parent->realpath(basename($this->rsrc)) : $this->rsrc->realpath();	
		}

		function is_readable(){
			return $this->test_permission('r');
		}
		
		function is_writable(){
			return $this->test_permission('w');
		}
		
		protected function test_permission($mode){
			$mode = substr($mode, 0, 1);
			if($this->handle){
				if(substr($this->mode, -1) == '+') return true;
				$t = substr($this->mode, 0, 1);
				return ($mode == $t) ? true : ($t != 'r');
			}
			$method = $this->is_smuoi ? 'smuoi_' : '';
			$method .= ($mode == 'r') ? 'is_readable' : 'is_writable';
			return $this->exists() ? $method($this->rsrc) : $method($this->parent);	
		}
		
		function is_resource(){
			return is_resource($this->handle);	
		}
	
		function open($mode=false){
			if(!$mode) $mode = $this->mode;
			if(!$this->is_valid) return false;
			if(($this->mode == $mode)&&($this->is_resource())) return true;
			if(!$this->can_open($mode)) return $this->open_error($mode);
			if($this->handle) $this->close(false);
			$this->handle = fopen($this->realpath(), $mode);
			if($h = is_resource($this->handle)) $this->mode = $mode;
			if(!$this->fexists) $this->fexists = $h;
			return $h;
		}
		
		function can_open($mode=false){
			if(!$mode) $mode = $this->mode;
			switch($mode){
				case 'r':
					return ($this->exists() && $this->is_readable());
				case 'r+':
					return ($this->exists() && $this->is_readable() && $this->is_writable());
				case 'w':
				case 'a':
					return $this->is_writable();
				case 'w+':
				case 'a+':
					return ($this->is_readable() && $this->is_writable());
				case 'x':
					return (!$this->exists() && $this->is_writable());
				case 'x+':
					return (!$this->exists() && $this->is_readable() && $this->is_writable());
			}
			trigger_error("rsrcHandle: given mode ($mode) is invalid", E_USER_NOTICE);
			return false;
		}
		
		function try_open($mode='r', $timeout=30, $statusCallBack=false){
			$n = $timeout*4;
			$i = 0;
			while(!$this->can_open($mode)){
				if($i == $n) return false;
				usleep(250000);
				$i++;
				if($statusCallBack && !call_user_func($statusCallBack)) return false;
			}
			return $this->open($mode);
		}
		
		function open_error($mode){
			$message = 	get_class($this).": could not open $this->rsrc with mode $mode (";
			$message .= 'exists: ' . ($this->exists() ? 'yes' : 'no');
			$message .= ', readable: ' . ($this->is_readable() ? 'yes' : 'no');
			$message .= ', writable: ' . ($this->is_writable() ? 'yes)' : 'no)');
			trigger_error($message, E_USER_NOTICE);
			return false;
		}
		
		function close($commit=true){
			if($this->handle && fclose($this->handle)){
				$this->handle = $this->mode = null;
				if($commit){
					if($this->is_smuoi){
						if($this->parent){
							$this->rsrc = $this->parent->child(basename($this->rsrc));
							$this->parent = null;
						}
						$this->rsrc->touch();
					}elseif($this->destroy) $this->destroy();	
				}
				return true;
			}
			return false;
		}
		
		function destroy(){
			return (!$this->is_smuoi && $this->is_writable()) ? !($this->fexists = !unlink($this->rsrc)) : false;
		}
		
		function __destruct(){
			$this->close();	
		}
		
	}

